package com.chenfan.mcn.controller;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.convert.Convert;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.chenfan.common.exception.BusinessException;
import com.chenfan.mcn.annotations.ActionTracing;
import com.chenfan.common.extension.context.SpringContextHolder;
import com.chenfan.mcn.enums.ApprovalEnum;
import com.chenfan.mcn.enums.OperateLockEnum;
import com.chenfan.mcn.exception.McnErrorCode;
import com.chenfan.mcn.extension.Res;
import com.chenfan.common.extension.lock.DistributedLockTemplate;
import com.chenfan.mcn.model.ApprovalFlow;
import com.chenfan.mcn.model.dto.ApprovalFlowDTO;
import com.chenfan.mcn.model.dto.ChangeApprovalDTO;
import com.chenfan.mcn.service.ApprovalCallbackService;
import com.chenfan.mcn.service.ApprovalFlowService;
import com.chenfan.mcn.utils.BusinessAssert;
import com.chenfan.mcn.utils.CommonUtil;
import com.chenfan.process.dto.ApprovalCallBack;
import com.chenfan.process.vo.ApprovalOperationVO;
import com.chenfan.process.vo.NextNotifyVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Objects;

/**
 * 审批流回调
 * @author 2062
 */
@Slf4j
@RestController
public class ApprovalFlowController {

    @Autowired
    private ApprovalFlowService approvalFlowService;

    /**
     * 审批流回调
     *
     * @param approval {@link ApprovalCallBack}
     * @return {@link Res}
     */
    @ActionTracing(value = "审批流回调", allAlarm = true)
    @PostMapping(value = "/callback")
    public Res<Object> callback(@RequestBody ApprovalCallBack approval) {
        log.info("触发审批流回调——————————————>" + JSON.toJSONString(approval));
        try {
            return Res.ok(DistributedLockTemplate.execute(OperateLockEnum.APPROVAL_FLOW.generateKey(approval.getApprovalId()), () -> {
                ApprovalFlow approvalFlow = getApprovalFlow(approval.getApprovalId(), 0);
                if (Objects.isNull(approvalFlow)) {
                    throw new BusinessException(McnErrorCode.DATA_NOT_FOUND.code(), approval.getApprovalId() + "不存在");
                }
                Boolean status = null;
                if (Objects.nonNull(approval.getApprovalFlag())) {
                    if (!Boolean.TRUE.equals(approval.getApprovalFlag())) {
                        status = false;
                    } else if (Boolean.TRUE.equals(approval.getApprovalFlag()) && approval.getApprovalFinished()) {
                        status = true;
                    }
                }
                Class<?> clazz = ApprovalEnum.getServiceClass(approvalFlow.getProcessId());
                Method method = ReflectionUtils.findMethod(Objects.requireNonNull(clazz), "approvalCallback", ApprovalFlowDTO.class, Boolean.class);
                if (Objects.isNull(method)) {
                    throw new BusinessException(McnErrorCode.FLOW_CALLBACK_METHOD_NOT_FOUND);
                }
                ApprovalFlowDTO dto = Convert.convert(ApprovalFlowDTO.class, approvalFlow);
                if (Objects.nonNull(approval.getNextApprovalUserId())) {
                    dto.setNextApprovalUserId(ListUtil.of(approval.getNextApprovalUserId().split(",")));
                    if (!StringUtils.isEmpty(approval.getNextApprovalUserName())) {
                        dto.setNextApprovalUserName(ListUtil.of(approval.getNextApprovalUserName().split(",")));
                    } else {
                        dto.setNextApprovalUserName(Collections.emptyList());
                    }
                }
                dto.setNodeName(approval.getNodeName());
                dto.setEventType(approval.getEventType());
                dto.setApprovalUserId(approval.getUserId());
                dto.setApprovalUserName(approval.getUserName());
                dto.setCarbonCopyList(approval.getCarbonCopyList());
                ReflectionUtils.invokeMethod(method, SpringContextHolder.getBean(clazz), dto, status);
                if (Objects.nonNull(status)) {
                    approvalFlow.setActiveStatus(false);
                    approvalFlowService.updateById(approvalFlow);
                }
                return null;
            }));
        } finally {
            log.info("<——————————————审批流回调结束");
        }
    }

    private ApprovalFlow getApprovalFlow(Long approvalId, int index) {
        int times = 5;
        ApprovalFlow approvalFlow = approvalFlowService.getOne(Wrappers.<ApprovalFlow>lambdaQuery().eq(ApprovalFlow::getApprovalId, approvalId));
        if (Objects.isNull(approvalFlow) && index < times) {
            try {
                Thread.sleep(500);
                log.info("审批流回调自旋！approvalId={},retry={}", approvalId, index+1);
            } catch (InterruptedException e) {
                log.error("自旋获取审批流异常中断！approvalId=%s,retry=%s", approvalId, index, e);
                if (Thread.currentThread().isInterrupted()) {
                    Thread.currentThread().interrupt();
                }
            } finally {
                approvalFlow = getApprovalFlow(approvalId, ++index);
            }
        }
        return approvalFlow;
    }

    /**
     * 审批流程
     * @param dto
     * @return
     */
    @ActionTracing("流程审批")
    @PostMapping(value = "/flow/changeApproval", produces = {"application/json;charset=UTF-8"})
    public Res<ApprovalOperationVO> changeApproval(@Validated @RequestBody ChangeApprovalDTO dto) {
        return Res.ok(DistributedLockTemplate.execute(OperateLockEnum.APPROVAL_FLOW.generateKey(dto.getApprovalId()), () -> {
                    ApprovalFlow approvalFlow = approvalFlowService.getOne(Wrappers.lambdaQuery(ApprovalFlow.class).eq(ApprovalFlow::getApprovalId, dto.getApprovalId()));
                    BusinessAssert.notNull(approvalFlow, McnErrorCode.FLOW_NOT_FOUND);
                    BusinessAssert.isTrue(approvalFlow.getActiveStatus().booleanValue(), McnErrorCode.FLOW_FINISHED);
                    Class<?> clazz = ApprovalEnum.getServiceClass(approvalFlow.getProcessId());
                    BusinessAssert.isTrue(Objects.nonNull(clazz) && ApprovalCallbackService.class.isAssignableFrom(clazz), McnErrorCode.DATA_OPERATION_NOT_SUPPORTED);
                    ApprovalCallbackService approvalCallbackService = (ApprovalCallbackService) SpringContextHolder.getBean(clazz);
                    dto.setParamMap(CommonUtil.defaultMap(dto.getParamMap()));
                    return approvalCallbackService.changeApproval(dto, approvalFlow.getSrcId());
        }));
    }

    /**
     * 审批流程并返回下一条
     * @param dto
     * @return
     */
    @ActionTracing("审批流程并返回下一条")
    @PostMapping(value = "/flow/changeApprovalNext", produces = {"application/json;charset=UTF-8"})
    public Res<NextNotifyVo> changeApprovalNext(@Validated @RequestBody ChangeApprovalDTO dto) {
        return Res.ok(DistributedLockTemplate.execute(OperateLockEnum.APPROVAL_FLOW.generateKey(dto.getApprovalId()), () -> {
            ApprovalFlow approvalFlow = approvalFlowService.getOne(Wrappers.lambdaQuery(ApprovalFlow.class).eq(ApprovalFlow::getApprovalId, dto.getApprovalId()));
            BusinessAssert.notNull(approvalFlow, McnErrorCode.FLOW_NOT_FOUND);
            BusinessAssert.isTrue(approvalFlow.getActiveStatus().booleanValue(), McnErrorCode.FLOW_FINISHED);
            Class<?> clazz = ApprovalEnum.getServiceClass(approvalFlow.getProcessId());
            BusinessAssert.isTrue(Objects.nonNull(clazz) && ApprovalCallbackService.class.isAssignableFrom(clazz), McnErrorCode.DATA_OPERATION_NOT_SUPPORTED);
            ApprovalCallbackService approvalCallbackService = (ApprovalCallbackService) SpringContextHolder.getBean(clazz);
            dto.setParamMap(CommonUtil.defaultMap(dto.getParamMap()));
            return approvalCallbackService.changeApprovalNext(dto, approvalFlow.getSrcId());
        }));
    }
}
